DartVM Isolate
介绍
Flutter/Dart 代码是事件驱动的,代码运行在 Isolate 当中。对于 Dart 开发来说,Isolate 相当于线程,基于 Isolate 可以实现无锁并发。
Isolate 之间不能相互访问,他们需要通过 Port 机制相互通信。
每个 isolate 都有一个关联线程(mutator thread),用于执行 Dart 代码。
针对 Isolate 有两个视角:开发者使用视角、实现原理视角。
开发者视角:Isolate 间通信
Isolate 间通信分类两种情况:单向通信和双向通信。
单向通信
Isolate 间通信的示例:
void demo() {
ReceivePort port = ReceivePort();
// 创建 isolate
isolate.spawn(func, port.sendPort);
port.listen((message) {
print(message)
});
}
void func(SendPort sendPort) {
sendPort.send(1)
}
其中:
- 创建了一个 ReceivePort,它是当前 Isolate 与新建 Isolate 沟通的桥梁
- 通过 isolate.spawn 创建了一个新 Isolate,并传入 ReceivePort 的 sendPort
- sendPort 是新 isolate 向老 Isolate 回传数据的一端
- 老 Isolate 通过对 ReceivePort 进行监听获取数据
双向通信
示例代码:
Future<void> demo() async {
// 接收新 Isolate 回传数据
final response = ReceivePort();
// 创建新 Isolate
await Isolate.spawn(func, response.sendPort);
// 新 Isolate 传回来一个 SendPort,用于向 Isolate 发送数据
final sendPort = await response.first as SendPort;
}
void func(SendPort sendPort) async {
final port = ReceivePort();
// 接收老 Isolate 传来数据
sendPort.send(port.sendPort);
}
上面代码中,只包含了双向通信打通的代码,没有包含消息监听的代码。
可以看到,SendPort 是可以跨 Isolate 传递的。
注:为啥 response 可以 await first 呢?因为 ReceivePort 实现了 Stream 接口。
Isolate 实现原理
Isolate 在虚拟机中有两个实现类:
- isolate.h:Isolate 的具体实现,runtime/vm/isolate.h
- isolate.dart:在 Dart 侧的映射,将操作转发到底层
- 接口 sdk/lib/isolate/isolate.dart
- 原生实现 sdk/lib/_internal/vm/lib/isolate_patch.dart
Isolate 是虚拟机底层特性,具体的实现都在 C/C++ 层中。Dart 侧的 Isolate 是 C 层在 Dart 层的映射,供 Dart 层访问 Isolate,具体操作都是转发到底层处理。
Isolate 内部由以下部分组成:

- Isolate 堆(Heap):由垃圾回收器 GC 管理的存储,这个 isolate 里运行的代码,创建的所有对象,都在这个堆中进行管理。
- mutator thread:每个 isolate 都有一个 mutator thread,用于执行 Dart 代码。
- MessageQueue:消息队列,细节:
- Isolate 的 MessageQueue 不是一个死循环,而是按需启动的。
- 当有新消息插入到 MessageQueue,如果 Isolate 尚未启动,VM 会将消息处理器以任务形式交给线程池,由线程池为其分配一个线程,在分配的线程上执行任务处理
- 从消息队列中取消息执行。如果消息队列为空,所有任务都执行完了,会出让该线程。
- 总结:Isolate 并不会长期占用一个线程,而是所有 Isolate 共享一个线程池。
- 消息队列的种类
- Isolate 消息处理器中有两种消息队列:普通消息队列和 OOB 消息队列。
- OOB 全称是 out of band,表示带外消息,用来传送一些控制类消息。如暂停(Pause)、恢复(Resume)、终止(Kill)
- Isolate 的 MessageQueue 不是一个死循环,而是按需启动的。
- helper thread:helper thread 不只一个,不同的线程执行不同工作,比如:
- GC 清理
- JIT 编译
- GC 并发标记
Isolate.kill 终止操作
在 Dart 侧可以调用 Isolate 的 kill 方法终止 Isolate,Dart 侧 Isolate 会通过消息队列机制调用 C 层 Isolate,具体流程是:
- Dart Isolate kill 方法被调用
- 生成一条 OOB Message,扔到消息队列上
- IsolateMessageHandler 响应 OOB Message
- 调用 C 层 Isolate 相关实现
注释介绍:请求 Isolate 终止。
需传入一个 priority 参数,有两种优先级:
- immediate:isolate 尽快终止
- 在 OOB 队列上,控制信息是按顺序处理的,因此消息队列上在终止信息之前的控制消息都会被处理。
- 终止完成的时间节点,不会比 beforeNextEvent 晚
- beforeNextEvent:
- 在当前事件和消息对列上已有的控制事件完成后,终止操作被安排在下一次 Isolate 收到控制信号的时候
kill 操作的方法签名:
external void kill({int priority = beforeNextEvent});
如果 terminateCapability 为空,或者它不是 controlPort 所标示的 Isolate 终止能力,则 Isolate 将忽略该终止请求。
具体实现:
Dart 侧 Isolate kill 方法被调用时,实际是发送一条 OOB 消息,抛到消息队列,isolate.dart(sdk/lib/_internal/vm/lib/isolate_patch.dart):
@patch
void kill({int priority: beforeNextEvent}) {
var msg = new List<Object?>.filled(4, null)
..[0] = 0 // Make room for OOB message type.
..[1] = _KILL
..[2] = terminateCapability
..[3] = priority;
_sendOOB(controlPort, msg);
}
这条消息在 IsolateMessageHandler 的 IsolateMessageHandler::HandleLibMessage 中被处理:
case Isolate::kKillMsg:
case Isolate::kInternalKillMsg: {
// [ OOB, kKillMsg, terminate capability, priority ]
const intptr_t priority = Smi::Cast(obj).Value();
// kImmediateAction
if (priority == Isolate::kImmediateAction) {
obj = message.At(2);
if (I->VerifyTerminateCapability(obj)) {
// We will kill the current isolate by returning an UnwindError.
if (msg_type == Isolate::kKillMsg) {
const String& msg = String::Handle(
String::New("isolate terminated by Isolate.kill"));
const UnwindError& error =
UnwindError::Handle(UnwindError::New(msg));
error.set_is_user_initiated(true);
return error.ptr();
} else if (msg_type == Isolate::kInternalKillMsg) {
const String& msg =
String::Handle(String::New("isolate terminated by vm"));
return UnwindError::New(msg);
} else {
UNREACHABLE();
}
} else {
return Error::null();
}
} else {
// kBeforeNextEventAction kAsEventAction
message.SetAt(
0, Smi::Handle(zone, Smi::New(Message::kDelayedIsolateLibOOBMsg)));
message.SetAt(3,
Smi::Handle(zone, Smi::New(Isolate::kImmediateAction)));
this->PostMessage(
SerializeMessage(Message::kIllegalPort, message),
priority == Isolate::kBeforeNextEventAction /* at_head */);
}
break;
}
这里面 if 嵌套有点多,最外层的判断优先级:kImmediateAction 和 kBeforeNextEventAction。
先看 kImmediateAction 的处理:
- 这里有个很重要的操作,VerifyTerminateCapability,如果认证失败的话,会什么都不做
return Error::null();
- 接下来要看 msg_type,就是 Switch 的 case,有两个可能 kKillMsg、kInternalKillMsg,Isolate.kill 发的值是 4,也就是 kKillMsg
- 如果是 kKillMsg,让 Isolate 停下来的方式,是抛出一个 UnwindError,这是一个威力特别大的 Error,抛出后 Isolate 就会终止
- 不论是 kKillMsg 还是 kInternalKillMsg。停 isolate 的手段是一样的,都是靠扔 UnwindError,区别在于 Error message 不一样
再看 kBeforeNextEventAction:
- 它是对 OOB 消息进行了修改
- 并重新扔到 OOB 消息队列中,新的消息的类型已经是 kImmediateAction
- 注意到 PostMessage 的第二个参数 priority,在消息队列执行出队的时候,只会出该优先级之上的消息
问题:kImmediateAction 终止不了
我在实践中遇到一个问题,Isolate.kill 传 kImmediateAction,结果 Isolate 没有终止。
如何看 Isolate 是否成功终止?去 Observatory 里面,他会展示运行中的 Isolate,如果终止成功,在 Observatory 里就没有这个 Isolate 了。
从命令行上来看,也有杀死的日志:[VERBOSE-2:shell.cc(93)] Dart Error: isolate terminated by Isolate.kill
问题的原因找到了,我是在 Demo 里,摆了一个按钮,在按钮回调中调用 Isolate.kill 的 kImmediateAction 优先级,这时候 Isolate.kill 会阻塞住执行不完。
如果我把 Isolate.kill 的 kImmediateAction 调用用 Future 封装一下再在点击回调中调用,就能成功终止了。
由此看来,点击回调跟 Isolate.kill 的 kImmediateAction 在消息队列上是存在某种冲突。冲突的原因还需要后续深挖。
问题:Future + kImmediateAction vs kBeforeNextEventAction 谁快?
尽管注释说 kImmediateAction 终止完成的时间节点,不会比 beforeNextEvent 晚,但这是没有套 Future 的时候。
一个 Future(Normal Message)的优先级是低于 OOB Message 的。
因此,Future + kImmediateAction vs kBeforeNextEventAction,kBeforeNextEventAction 速度更快。
Kernel Isolate
又被称为 Kernel Service。代码位于 runtime/vm/kernel_isolate.h。通过阅读参考文献1,直到它的核心工作时 Common front end,负责将 Dart 源码转为 Kernel 二进制,Dart VM 可以接收这个二进制在主 Isolate 里面运行。